Charts
The Blotch Library has ready-made charts for showing numbers at a glance. They are drawn in black and white, and tell series apart with dashes and fill patterns instead of colors, so they stay readable on an E-ink display. They also switch to light-on-dark automatically when the Frame uses dark mode.
import { LineChart } from "~lib/charts/line-chart";
const Widget = () => <LineChart data={[12, 18, 15, 22, 30]} />;
Every chart is imported from ~lib/charts/<name>. This page covers the three you will reach for most often. The rest are listed at the end.
Charting fetched data
Charts take plain arrays, so turn the API's response into one with map. Because useFetch waits for the data before your Widget renders, there is nothing to check first:
import { LineChart } from "~lib/charts/line-chart";
import { useFetch } from "~lib/use-fetch";
type Reading = {
readonly time: string;
readonly temperature: number;
};
const Widget = () => {
const { data } = useFetch<Reading[]>("https://api.example.com/readings");
return (
<LineChart
data={data.map((reading): [Date, number] => [
new Date(reading.time),
reading.temperature,
])}
axis={{ x: { visible: true }, y: { visible: true, name: "°C" } }}
width="100%"
height="100%"
/>
);
};
BarIndicator
A horizontal bar filled to show how far one number is towards another, such as storage used or a goal reached.
import { BarIndicator } from "~lib/charts/bar-indicator";
<BarIndicator numerator={75} denominator={100} />;
The fill stays between empty and full: a numerator above denominator shows a full bar, and a negative one shows an empty bar.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
numerator | number | Yes | The current value. | |
denominator | number | Yes | The value of a full bar. | |
width | number | string | No | "100%" | The bar's width. See Sizing. |
height | number | string | No | A slim bar | The bar's height. See Sizing. |
DonutGauge
A ring filled to show one number as a share of another, such as battery level or a percentage.
import { DonutGauge } from "~lib/charts/donut-gauge";
<DonutGauge numerator={7} denominator={10} pattern="diagonal" />;
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
numerator | number | Yes | The current value. | |
denominator | number | Yes | The value of a full ring. | |
pattern | "solid" | "diagonal" | "dots" | "crosshatch" | No | "solid" | How the filled part of the ring is drawn. |
width | number | string | No | 300 | See Sizing. |
height | number | string | No | 300 | See Sizing. |
LineChart
Plots values as a connected line, for trends over time.
import { LineChart } from "~lib/charts/line-chart";
<LineChart data={[12, 18, 15, 22, 30]} />;
A chart needs at least two points. With fewer, it draws nothing.
Points
data accepts whichever shape is closest to what your API returns:
| Shape | Example | Notes |
|---|---|---|
number[] | [12, 18, 15] | Values only. They are spaced evenly. |
[number, number][] | [[0, 12], [5, 18], [7, 15]] | [x, y] pairs. Points can be spaced unevenly. |
{ x, y }[] | [{ x: 0, y: 12 }, { x: 5, y: 18 }] | The same as pairs, as objects. |
[Date, number][] | [[new Date("2025-01-01"), 12], ...] | Dates along the x axis. The axis labels become dates. |
[x, y, extra][] | [[new Date("2025-01-01"), 12, "☀️"]] | A third item of your own, available to dataLabel. |
If your dates are timestamps in milliseconds rather than Date objects, set axis.x.type to "date" to get date labels.
Several lines
Pass an object instead of an array to draw one line per key. Each line gets its own dash style, and legend names them:
<LineChart
data={{
London: [12, 14, 13, 17],
Paris: [15, 16, 18, 19],
}}
legend={{ position: "top-right" }}
/>
Set stacked to draw each line on top of the ones before it, so the top line shows the total.
Axes
Axes are hidden by default. Turn each on with visible, and label it with name:
<LineChart
data={[
[new Date("2025-01-01"), 120],
[new Date("2025-02-01"), 135],
[new Date("2025-03-01"), 128],
]}
axis={{
x: { visible: true, dateFormat: { month: "short" } },
y: { visible: true, name: "Visitors", min: 0, grid: true },
}}
/>
| Option | Applies to | Description |
|---|---|---|
visible | x, y | Shows the axis and its labels. |
name | x, y | A title along the axis. |
grid | x, y | Draws dotted grid lines across the chart. |
min | y | Where the axis starts. It otherwise starts at the lowest value, so set 0 to stop a small change looking like a big one. |
type | x | Set to "date" when the x values are timestamps in milliseconds. |
dateFormat | x | How dates are written, as Intl.DateTimeFormat options. |
Labelling points
dataLabel puts something next to each point. It receives the point, its position in the list and the name of its line:
const days: [Date, number, string][] = [
[new Date("2025-06-01"), 21, "☀️"],
[new Date("2025-06-02"), 17, "🌧️"],
[new Date("2025-06-03"), 19, "⛅"],
];
<LineChart data={days} dots dataLabel={(_point, index) => days[index][2]} />;
dataIcon sets an icon above a point's label, at a size that reads on its own. Return the icon's name, or null for none:
const hours: [Date, number, string][] = [
[new Date("2025-06-01T14:00"), 21, "sun"],
[new Date("2025-06-01T15:00"), 19, "cloud-rain"],
];
<LineChart
data={hours}
dots
axis={{ y: { max: 30 } }}
dataLabel={(point) => `${point[1]}°`}
dataIcon={(point) => point[2]}
/>;
Give the line some room above it with axis.y.max, so every icon fits over its point.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | Points, or an object of points per line | Yes | See Points and Several lines. | |
axis | { x?: {...}, y?: {...} } | No | Hidden | See Axes. |
width | number | string | No | 300 | See Sizing. |
height | number | string | No | 300 | See Sizing. |
smooth | boolean | No | false | Draws curves between points instead of straight lines. |
dots | boolean | No | false | Marks each point with a dot. |
stacked | boolean | No | false | Stacks each line on top of the ones before it. |
legend | boolean | { position: "top" | "top-left" | "top-right" | "bottom" | "bottom-left" | "bottom-right" } | No | No legend | Names each line. Only shown when there is more than one line. true places it at the bottom. |
direction | "horizontal" | "vertical" | No | "horizontal" | "vertical" turns the chart on its side. |
dataLabel | (point, index, lineName) => ReactNode | No | See Labelling points. | |
dataIcon | (point, index, lineName) => string | null | No | An icon's name to set above a point. See Labelling points. |
Sizing
width and height take a number of pixels (200) or any CSS size ("100%", "12rem"). To have a chart fill the space it is given, set both to "100%", which keeps it looking right as the Widget is resized.
A chart set to "100%" also fills its box in every preview of your Widget. Lines, areas and bars stretch to the box, while text, dots and patterns stay the same size. Round charts, such as PieChart and DonutGauge, keep their shape and grow or shrink to fit instead.
More charts
These are also in the Blotch Library. Each link opens its live examples and full list of props.
| Chart | Import | Good for |
|---|---|---|
| AreaChart | ~lib/charts/area-chart | A line with the area below it filled. |
| BarChart | ~lib/charts/bar-chart | Comparing values side by side, upright or on its side. |
| CandlestickChart | ~lib/charts/candlestick-chart | Open, high, low and close prices. |
| ContributionGrid | ~lib/charts/contribution-grid | A year of activity, a square per day. |
| FunnelChart | ~lib/charts/funnel-chart | Stages that narrow, such as sign-ups becoming customers. |
| Heatmap | ~lib/charts/heatmap | Intensity across a grid, such as activity per day. |
| PieChart | ~lib/charts/pie-chart | Parts of a whole. |
| RadarChart | ~lib/charts/radar-chart | Comparing several measures at once. |
| SankeyChart | ~lib/charts/sankey-chart | Amounts flowing from one place to another. |
| ScatterPlot | ~lib/charts/scatter-plot | Individual points, without a line joining them. |
| Sparkline | ~lib/charts/sparkline | A tiny trend line with no axes, to sit beside a number. |
| SurfaceChart | ~lib/charts/surface-chart | A 3D surface of values over a grid. |
| TreeMap | ~lib/charts/tree-map | Parts of a whole, as nested rectangles sized by value. |